﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using System.ServiceModel.Syndication;
using System.Runtime.Remoting.Messaging;
using System.ComponentModel;
using HtmlAgilityPack;
using System.Threading;
using System.Collections.Specialized;
using System.Net.NetworkInformation;
using Timer = System.Threading.Timer;
using System.Text.RegularExpressions;

namespace Nwsfeed
{
    public class WebHelper
    {
        public static bool IsNetworkAvailable { get; protected set; }

        public WebHelper()
        {
            onCompletedDiscoverFeedsDelegate = new SendOrPostCallback(DiscoverFeedsCompletedDelegateFunc);
        }

        static WebHelper()
        {
            Initialize();
        }

        private static void Initialize()
        {
            StartNetworkObservation();
        }

        // NETWORK STATUS OBSERVATION
        public static event EventHandler<NetworkChangedEventArgs> NetworkChanged;
        private static Timer timer;

        private static bool isRunning = false;
        private static readonly object locker = new object();
        public static void StartNetworkObservation()
        {
            if (!isRunning)
            {
                lock (locker)
                {
                    try
                    {
                        string address = "www.google.com";
                        IPAddress[] addressList = Dns.GetHostAddresses(address);

                        if (addressList[0].ToString().Length > 6)
                            IsNetworkAvailable = true;
                        else
                            IsNetworkAvailable = false;
                    }
                    catch
                    {
                        IsNetworkAvailable = false;
                    }

                    timer = new Timer(UpdateNetworkStatus, null, new TimeSpan(0, 0, 0, 0, 100), new TimeSpan(0, 0, 0, 0, 5000));
                    isRunning = true;
                }
            }
        }

        private static void UpdateNetworkStatus(object state)
        {
            bool prevState = IsNetworkAvailable;
            try
            {
                string address = "www.google.com";
                IPAddress[] addressList = Dns.GetHostAddresses(address);

                if (addressList[0].ToString().Length > 6)
                    IsNetworkAvailable = true;
                else
                    IsNetworkAvailable = false;
            }
            catch
            {
                IsNetworkAvailable = false;
            }

            if (NetworkChanged != null && (prevState != IsNetworkAvailable))
                NetworkChanged.Invoke(null, new NetworkChangedEventArgs(IsNetworkAvailable));
        }

        // FEED DISCOVERY
        private delegate void DiscoverFeedsWorkerEventHandler(Uri uri, AsyncOperation asyncOp);
        private SendOrPostCallback onCompletedDiscoverFeedsDelegate;
        private HybridDictionary tasks = new HybridDictionary();
        public event DiscoverFeedsCompletedEventHandler DiscoverFeedsCompleted;

        public List<RSSFeed> DiscoverFeeds(Uri uri)
        {
            Exception exception = null;
            List<RSSFeed> feedsDsicovered = new List<RSSFeed>();

            // WebClient is used in case computer is behing a proxy
            MyWebClient client = new MyWebClient();
            client.Proxy = WebRequest.DefaultWebProxy;
            client.Proxy.Credentials = CredentialCache.DefaultCredentials;
            client.Credentials = CredentialCache.DefaultCredentials;

            // check if page exists, try to download only the header
            // no exception = page can be loaded
            try
            {
                client.HeadOnly = true;
                client.DownloadString(uri);
            }
            catch (Exception ex)
            {
                // page doesn't exist or cannot be loaded
                exception = ex;
                return feedsDsicovered;
            }

            client.HeadOnly = false;

            // download the page
            // what if url is link to a feed itself? for now it freezes...
            HtmlDocument doc = new HtmlDocument();
            String str = client.DownloadString(uri);
            if (str != null)
                doc.LoadHtml(str);
            else
                return feedsDsicovered;

            if (doc != null)
            {
                HtmlNodeCollection htmlLinkNodes = null;
                bool isFeedItself = false;
                #region Search for RSS/Atom feeds using autodiscovery tags
                // DETECT DOCUMENT TYPE
                try
                {
                    htmlLinkNodes = doc.DocumentNode.SelectSingleNode("//head").SelectNodes("//link");
                }
                catch
                {
                    // probably not HTML
                    // check if doc is RSS feed itself
                    try
                    {
                        HtmlNode node = doc.DocumentNode.SelectSingleNode("//rss");
                        // rss feed itself
                        isFeedItself = true;
                    }
                    catch
                    {
                        // not valid doc
                        return feedsDsicovered;
                    }
                }

                if (htmlLinkNodes != null && !isFeedItself)
                {
                    foreach (HtmlNode node in htmlLinkNodes)
                    {

                        // TO DO: handle all valid attribute forms (e.g. rel="Alternate")
                        // see http://philringnalda.com/rfc/draft-ietf-atompub-autodiscovery-01.html for more examples of valid forms                    

                        // TO DO: uri could be relative, if so, search for <base> or try to reconstruct the uri
                        if (node.GetAttributeValue("rel", "") == "alternate" &&
                            (node.GetAttributeValue("type", "") == "application/rss+xml"
                            || node.GetAttributeValue("type", "") == "application/atom+xml"))
                        {
                            Uri _uri = null;
                            if (Uri.IsWellFormedUriString(node.GetAttributeValue("href", ""), UriKind.Absolute))
                                _uri = new Uri(node.GetAttributeValue("href", ""));
                            else
                            {
                                _uri = new Uri(uri.AbsoluteUri.ToString() + "/" + node.GetAttributeValue("href", ""));
                            }

                            try
                            {
                                HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(_uri);
                                myHttpWebRequest.Timeout = 100;
                                using (HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse())
                                {
                                    if (myHttpWebResponse.ContentEncoding != "")
                                    {
                                        System.Text.Encoding enc = System.Text.Encoding.GetEncoding(myHttpWebResponse.ContentEncoding);
                                        client.Encoding = enc;
                                    }
                                    else
                                        client.Encoding = System.Text.Encoding.UTF8;
                                }
                            }
                            catch (Exception ex)
                            {
                                client.Encoding = System.Text.Encoding.UTF8;
                            }

                            //str = DownloadPage(_uri.ToString());
                            str = client.DownloadString(_uri);
                            if (str != null)
                            {
                                str = Regex.Replace(str, "<!--.*?-->", String.Empty, RegexOptions.Singleline);
                                FeedXmlReader reader = new FeedXmlReader();
                                feedsDsicovered.Add(reader.Load(str, _uri));
                            }
                        }
                    }
                }

                if (isFeedItself)
                {
                    str = Regex.Replace(str, "<!--.*?-->", String.Empty, RegexOptions.Singleline);

                    FeedXmlReader reader = new FeedXmlReader();
                    feedsDsicovered.Add(reader.Load(str, uri));
                }
                #endregion

                // TO DO: search for feedburner feeds
            }

            client.Dispose();
            return feedsDsicovered;
        }

        public void discoverFeedsWorker(Uri uri, AsyncOperation asyncOp)
        {
            lock (tasks.SyncRoot)
            {
                tasks.Remove(asyncOp.UserSuppliedState);
            }

            DiscoverFeedsCompletedEventArgs e
                = new DiscoverFeedsCompletedEventArgs(DiscoverFeeds(uri), null, false, asyncOp.UserSuppliedState);
            asyncOp.PostOperationCompleted(onCompletedDiscoverFeedsDelegate, e);
        }

        private void DiscoverFeedsCompletedDelegateFunc(object operationState)
        {
            DiscoverFeedsCompletedEventArgs e
                = operationState as DiscoverFeedsCompletedEventArgs;

            if (DiscoverFeedsCompleted != null)
                DiscoverFeedsCompleted(this, e);
        }

        public void DiscoverFeedsAsync(Uri uri, object userState)
        {
            AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(userState);

            lock (tasks.SyncRoot)
            {
                if (tasks.Contains(userState))
                    throw new ArgumentException("User state parameter muust be unique", "userState");

                tasks[userState] = asyncOp;
            }

            DiscoverFeedsWorkerEventHandler worker
                = new DiscoverFeedsWorkerEventHandler(discoverFeedsWorker);
            worker.BeginInvoke(uri, asyncOp, null, null);
        }

        public string DownloadPage(string url)
        {
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);

            using (HttpWebResponse resp = (HttpWebResponse)req.GetResponse())
            {
                byte[] buffer;
                using (Stream s = resp.GetResponseStream())
                {
                    buffer = ReadStream(s);
                }

                string pageEncoding = "";
                Encoding e = Encoding.UTF8;
                if (resp.ContentEncoding != "")
                    pageEncoding = resp.ContentEncoding;
                else if (resp.CharacterSet != "")
                    pageEncoding = resp.CharacterSet;
                else if (resp.ContentType != "")
                    pageEncoding = GetCharacterSet(resp.ContentType);

                if (pageEncoding == "")
                    pageEncoding = GetCharacterSet(buffer);

                if (pageEncoding != "")
                {
                    try
                    {
                        e = Encoding.GetEncoding(pageEncoding);
                    }
                    catch
                    {
                        // invalid encoding
                    }
                }

                string data = e.GetString(buffer);

                return data;
            }
        }


        private string GetCharacterSet(string s)
        {
            s = s.ToUpper();
            int start = s.LastIndexOf("CHARSET");
            if (start == -1)
                return "";

            start = s.IndexOf("=", start);
            if (start == -1)
                return "";

            start++;
            s = s.Substring(start).Trim();
            int end = s.Length;

            int i = s.IndexOf(";");
            if (i != -1)
                end = i;
            i = s.IndexOf("\"");
            if (i != -1 && i < end)
                end = i;
            i = s.IndexOf("'");
            if (i != -1 && i < end)
                end = i;
            i = s.IndexOf("/");
            if (i != -1 && i < end)
                end = i;

            return s.Substring(0, end).Trim();
        }

        private string GetCharacterSet(byte[] data)
        {
            string s = Encoding.Default.GetString(data);
            return GetCharacterSet(s);
        }

        private byte[] ReadStream(Stream s)
        {
            long curLength;
            try
            {
                byte[] buffer = new byte[8096];
                using (MemoryStream ms = new MemoryStream())
                {
                    while (true)
                    {
                        int read = s.Read(buffer, 0, buffer.Length);
                        if (read <= 0)
                        {
                            curLength = 0;
                            return ms.ToArray();
                        }
                        ms.Write(buffer, 0, read);
                        curLength = ms.Length;
                    }
                }
            }
            catch (Exception ex)
            {
                return null;
            }
        }
    }

    public delegate void DiscoverFeedsCompletedEventHandler(object sender, DiscoverFeedsCompletedEventArgs e);

    public class DiscoverFeedsCompletedEventArgs : AsyncCompletedEventArgs
    {
        public DiscoverFeedsCompletedEventArgs(List<RSSFeed> result, Exception ex, bool canceled, object userState)
            : base(ex, canceled, userState)
        {
            this.Result = result;
        }

        public List<RSSFeed> Result { get; protected set; }
    }

    public class NetworkChangedEventArgs : EventArgs
    {
        public NetworkChangedEventArgs(bool isNetworkLikelyAvailable)
        {
            IsNetworkLikelyAvailable = isNetworkLikelyAvailable;
        }

        public bool IsNetworkLikelyAvailable { get; protected set; }
    }
}
